// i.r(t), i.d(t, { default: () => PortalPuckMeshModule })
import * as THREE from "three"
import CameraData from "../data/CameraData"
import SweepData from "../data/SweepData"
import EngineContext from "../EngineContext"
import resolutionEnum from "../enum/resolution.enum"
import sweepPlacementEnum from "../enum/sweepPlacement.enum"
import SweepTextureLoader from "../loader/SweepTextureLoader"
import T from "../math/51263"
import w from "../math/57051"
import Sweep from "../object/Sweep"
import u from "../packages/89352"
import PortalPuckRenderer from "../renderer/PortalPuckRenderer"
import c from "../shader/64506"
import a from "../shader/95432"
import SymbolList from "../SymbolList"
import funcUtil from "../utils/func.util"
import Logger from "../utils/Logger"
import FloorDataModule from "./FloorDataModule"
import InputIniModule from "./InputIniModule"
import ModelDataModule from "./ModelDataModule"
import Module from "./Module"
import RaycasterModule from "./RaycasterModule"
import RenderToTextureModule from "./RenderToTextureModule"
import WebGLRendererModule from "./WebGLRendererModule"
class PortalViewportMaterial extends THREE.RawShaderMaterial {
  static uniforms: any
  constructor() {
    const e = THREE.UniformsUtils.clone(PortalViewportMaterial.uniforms)
    super({
      vertexShader: a,
      fragmentShader: c,
      uniforms: e,
      name: "PortalViewportMaterial",
      side: THREE.BackSide
    })
  }
  updateTexture(e) {
    this.uniforms.tMap.value = e
  }
}
PortalViewportMaterial.uniforms = { tMap: { type: "t", value: null } }
export class PortalViewportLoader {
  tempLookDirection: THREE.Vector3
  worldCamera: THREE.PerspectiveCamera
  camera: THREE.Camera
  renderSize: number
  sweepTextureLoader: SweepTextureLoader
  textureRenderer: RenderToTextureModule
  renderTargets: Record<string, THREE.WebGLRenderTarget>
  renderCube: THREE.Mesh<THREE.BoxGeometry, PortalViewportMaterial>
  constructor(e, t, i, n) {
    this.tempLookDirection = new THREE.Vector3()
    this.worldCamera = n
    this.camera = new THREE.Camera()
    this.renderSize = i
    this.sweepTextureLoader = e
    this.textureRenderer = t
    this.renderTargets = {}
    this.renderCube = new THREE.Mesh(new THREE.BoxGeometry(4, 4, 4), new PortalViewportMaterial())
  }
  async getPortalTexture(e: Sweep, t: THREE.Quaternion, i: THREE.Vector3) {
    const n = this.renderTargets[e.id] || this.textureRenderer.createRenderTarget2D(this.renderSize, this.renderSize)
    const s = await this.sweepTextureLoader.load(e, resolutionEnum.BASE)
    this.renderCube.material.updateTexture(s)
    this.renderCube.quaternion.copy(t)
    this.camera.projectionMatrix.copy(this.worldCamera.projectionMatrix)
    this.worldCamera.getWorldPosition(this.tempLookDirection)
    this.camera.lookAt(this.tempLookDirection.sub(i).negate())
    this.textureRenderer.render(n, this.renderCube, this.camera)
    return n.texture
  }
  releasePortalTexture(e: string) {
    const t = this.renderTargets[e]
    t && this.textureRenderer.disposeRenderTarget2D(t)
  }
}
const _ = new Logger("sweep-portal-mesh")

export interface portalItem {
  index: number
  toSweep: Sweep
  fromSweep: Sweep
  position: THREE.Vector3
  lookDirection: THREE.Vector3
  billboard: boolean
  toExterior: boolean
  fromInterior: boolean
}
export default class PortalPuckMeshModule extends Module {
  portalCount: number
  portalData: Record<string, portalItem[]>
  raycaster: RaycasterModule
  portalRenderer: PortalPuckRenderer
  constructor() {
    super(...arguments), (this.name = "sweep-portal-mesh"), (this.portalCount = 0)
  }
  async init(e, t: EngineContext) {
    const [i, n, r, o, a, l, c] = await Promise.all([
      t.getModule(ModelDataModule),
      t.getModule(RaycasterModule),
      t.getModuleBySymbol<InputIniModule>(SymbolList.INPUT),
      t.getModule(RenderToTextureModule),
      t.getModuleBySymbol<WebGLRendererModule>(SymbolList.WEBGL_RENDERER),
      t.market.waitForData(SweepData),
      t.market.waitForData(CameraData),
      t.getModule(FloorDataModule)
    ])
    const h = t.claimRenderLayer(this.name)
    this.portalData = {}
    const u = a.getScene().camera
    const g = new SweepTextureLoader(i.getSignedUrls())
    this.raycaster = n
    const w = new PortalViewportLoader(g, o, 256, u)
    const _ = a.getScene().scene
    this.portalRenderer = new PortalPuckRenderer(_, r, w, h, c)
    const M = sweepPlacementEnum.MANUAL
    const x = l.filter(e => T.H7(e))
    l.iterate(e => {
      this.portalData[e.id] || (this.portalData[e.id] = []), T.H7(e) && this.addPortals(e, this.portalRenderer, l, x)
      let t = e.placementType
      this.bindings.push(
        e.onPropertyChanged("placementType", i => {
          t !== M && i === M && this.addPortals(e, this.portalRenderer, l, x), t === M && i !== M && this.removePortals(e.id, this.portalRenderer), (t = i)
        }),
        e.onChanged(() => {
          T.H7(e) && (this.removePortals(e.id, this.portalRenderer), this.addPortals(e, this.portalRenderer, l, x))
        })
      )
    })
    t.addComponent(this, this.portalRenderer)
  }
  filter(e) {
    this.portalRenderer.filter(e)
  }
  getPortalToExterior(e) {
    return this.portalRenderer.mapSweepToMesh(e)
  }
  removePortals(e, t) {
    for (const i of this.portalData[e]) t.removePortal(i)
    this.portalData[e] = []
  }
  addPortals(e: Sweep, t: PortalPuckRenderer, i: SweepData, n: Sweep[]) {
    const s = this.nearestAlignedSweep(e, i)
    if (s) {
      const i = this.entryLinks(e, s),
        r = this.neighborLinks(e, n)
      this.portalData[e.id] = i.concat(r)
      for (const i of this.portalData[e.id]) t.addPortal(i)
    } else _.debug(`Couldn't find the nearest sweep for a 360 on floor ${e.floorId}; not adding portals`)
  }
  nearestAlignedSweep(e, t: SweepData) {
    const i = [T.ff(e), T._k(), T._T(), T.b1(e)]
    const n = [w.TE(e.position)]
    const s = t.sortByScore(i, n)[0]
    return s ? s.sweep : null
  }
  modelIntersection(e: Sweep, t: Sweep, i = 1 / 0) {
    const n = new THREE.Vector3().copy(e.position).sub(t.position).setY(0).normalize()
    const s: THREE.Intersection = this.raycaster.picking.pick(t.position, n, funcUtil.isModelColliderTarget)
    return {
      intersect: s && s.distance <= i ? s : null,
      rayDirection: n
    }
  }
  entryLinks(e: Sweep, t: Sweep) {
    const i = []
    const n = e.position.distanceTo(t.position)
    const s = this.modelIntersection(e, t, n + u.vX)
    let r
    let a: THREE.Vector3
    let l = !1
    s.intersect && s.intersect.face
      ? ((r = s.intersect.face.normal.clone().setY(0).normalize()), (a = s.intersect.point.clone().addScaledVector(r, u.AF)))
      : ((r = null), (l = !0), (a = e.position.clone())),
      (a.y = t.position.y),
      i.push({
        index: this.portalCount++,
        toSweep: e,
        fromSweep: t,
        position: a,
        lookDirection: r,
        billboard: l,
        toExterior: !0,
        fromInterior: !0
      }),
      i.push({
        index: this.portalCount++,
        toSweep: t,
        fromSweep: e,
        position: new THREE.Vector3().lerpVectors(e.position, t.position, 2 / n).setY(e.position.y),
        lookDirection: r ? r.clone().negate() : null,
        billboard: l,
        toExterior: !1,
        fromInterior: !1
      })
    return i
  }
  neighborLinks(e: Sweep, t: Sweep[]) {
    const i = []
    for (const n of t)
      if (e !== n && n.floorId === e.floorId && e.position.distanceTo(n.position) < u.M0) {
        const t = e.position.distanceTo(n.position)
        if (this.modelIntersection(e, n, t).intersect || this.modelIntersection(n, e, t).intersect) continue
        const s = e.position.clone().sub(n.position).setY(0).normalize(),
          r = n.position.clone(),
          o = u.iz
        t > o && r.addScaledVector(s, t - o),
          i.push({
            index: this.portalCount++,
            toSweep: n,
            fromSweep: e,
            position: r,
            lookDirection: s,
            billboard: !1,
            toExterior: !0,
            fromInterior: !1
          })
      }
    return i
  }
}
